/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package mux

import (
	"fmt"
	"net"
	"bufio"
	"gitee.com/tugoer/orivil/route"
	"net/http"
	"gitee.com/tugoer/orivil/swagger"
	"os"
	"path/filepath"
	"strings"
	"gitee.com/tugoer/orivil/param"
	"reflect"
	"strconv"
	"gitee.com/tugoer/orivil/trace"
	"gitee.com/tugoer/orivil/log"
	"time"
	"gitee.com/tugoer/orivil/session"
	log2 "log"
)

type ContextHandler func(ctx *Context)

var Handler = NewHttpHandler()

type ActionError struct {
	Action string
	Route  string
	Err    string
}

func (ae *ActionError) Error() string {
	return fmt.Sprintf("action: [%s]\n route: [%s]\n error: %s\n", ae.Action, ae.Route, ae.Err)
}

type HttpHandler struct {
	router          *route.Router
	TplHandler      TemplateHandler
	NotFoundHandler http.HandlerFunc
	TplContainer *TemplateContainer
	API2            *swagger.OpenApi2
	SessionClient *session.Client

	actions    []*Action
	operations map[*Action]*swagger.Operation
}

func NewHttpHandler() *HttpHandler {
	err := os.MkdirAll(Env.Dir.Views, 0777)
	if err != nil {
		panic(err)
	}
	h := &HttpHandler{
		router:          route.NewRouter(),
		NotFoundHandler: http.NotFound,
		TplContainer:    &TemplateContainer{},
		API2:            swagger.API2,
		TplHandler:      NewTplContainer(nil),
		operations:      make(map[*Action]*swagger.Operation, 5),
	}
	return h
}

var StaticHandler = http.FileServer(http.Dir("./"))

func (h *HttpHandler) AddTemplates(ts Templates) {
	err := h.TplContainer.AddTemplates(ts)
	if err != nil {
		panic(err)
	}
}

// HandleStatic 处理静态请求, dir 只能为 "./" 目录下的子目录, 所有匹配请求的 Url 都将被去除路径前缀 stripPrefix
func (h *HttpHandler) HandleStatic(dir, stripPrefix string) {
	handler := http.StripPrefix(stripPrefix, StaticHandler)
	dir = filepath.Clean(dir)
	dir = filepath.ToSlash(dir)
	dir = strings.TrimSuffix(dir, "/") + "/"
	action := &Action{
		Method: http.MethodGet,
		Route:  dir,
		Handler: func(ctx *Context) {
			handler.ServeHTTP(ctx.Response, ctx.Request)
		},
	}
	// 如果路径已存在, 则跳过注册
	h.addActions([]*Action{action}, false)
}

func AddTemplates(ts Templates) {
	Handler.AddTemplates(ts)
}

func HandleStatic(dir, stripPrefix string) {
	Handler.HandleStatic(dir, stripPrefix)
}

func (h *HttpHandler) Handle(method, route string, handler ContextHandler) {
	action := &Action{
		Method:  method,
		Route:   route,
		Handler: handler,
	}
	h.AddActions(action)
}

func Handle(method, route string, handler ContextHandler) {
	Handler.Handle(method, route, handler)
}

func AddActions(actions ...*Action) {
	Handler.AddActions(actions...)
}

func (h *HttpHandler) AddActions(actions ...*Action) {
	h.addActions(actions, true)
}

func (h *HttpHandler) addActions(actions []*Action, must bool) {
	for _, action := range actions {
		var err error
		if must {
			err = h.router.AddAction(action)
		} else {
			err = h.router.AddActionIfNotExist(action)
		}
		if err != nil {
			panic(err)
		}
		if action.Params != nil {
			action.parser = param.RegisterParameters(action.Params)
		}
		h.actions = append(h.actions, action)
		if doc := action.ApiDoc; doc != nil {
			if action.Name == "" {
				panic(fmt.Errorf("action path: %s, the action name is empty", action.Route))
			}
			if doc.Summary == "" {
				doc.Summary = action.Name
			}
			if doc.Tags == nil {
				panic(fmt.Errorf("action [%s]: the api tag is not set", action.Name))
			}

			// "/user/:username" => "/user/{username}"
			path := ""
			_, pathParams, isPattern, _ := route.Path(action.Route).GetParams(func(node string, isParam bool) error {
				if isParam {
					path += "/{" + node + "}"
				} else {
					path += "/" + node
				}
				return nil
			})
			if isPattern {
				path += "/"
			}

			for _, p := range action.Params {
				// 检测路径参数及解析参数的一致性
				if p.In == param.Path {
					rt := param.ReflectTypeOf(p.Schema, param.NewFieldNameProvider(p.Ignore), p.Validator)
					var fields = make(map[string]bool, len(pathParams))
					rt.Range(func(kind reflect.Kind, field reflect.StructField, pvd param.ConditionProvider) {
						fields[field.Name] = true
					})
					for _, path := range pathParams {
						if !fields[path] {
							panic(&ActionError{
								Action: action.Name,
								Route:  action.Route,
								Err:    fmt.Sprintf("parse path parameter: need field '%s'", path),
							})
						}
					}
				}

				// 检测方法名是否有误
				if p.In == param.Form {
					switch action.Method {
					case http.MethodPut, http.MethodPatch, http.MethodPost:
					default:
						panic(&ActionError{
							Action: action.Name,
							Route:  action.Route,
							Err:    "form data only support for PUT, PATCH and POST method, got: " + action.Method,
						})
					}
				}
			}
			op := &swagger.Operation{
				Tags:        doc.Tags,
				Summary:     doc.Summary,
				Description: doc.Description,
				OperationId: action.Name,
			}
			// 添加参数
			op.AddParameters(action.Params)

			// 获得定义的响应
			responses := doc.Responses.Transform()

			// 添加内置消息响应模型(服务器出错时或主动发送消息时会用到该消息模型)
			msgStatus := strconv.Itoa(http.StatusAccepted)
			// 如果数字 202 键被占用
			if _, ok := responses[msgStatus]; ok {
				// 则使用 "StatusAccepted" 字符串的形式表示(服务器实际仍然返回的是 202 状态码)
				msgStatus = http.StatusText(http.StatusAccepted)
			}
			responses[msgStatus] = &swagger.Response{
				Description: "server message",
				Schema:      swagger.NewJsonTypeSchema("", &message{}),
			}

			errStatus := strconv.Itoa(http.StatusInternalServerError)
			// 如果数字 202 键被占用
			if _, ok := responses[errStatus]; ok {
				// 则使用 "StatusAccepted" 字符串的形式表示(服务器实际仍然返回的是 202 状态码)
				errStatus = http.StatusText(http.StatusInternalServerError)
			}
			responses[errStatus] = &swagger.Response{
				Description: "server error, code types[100:warning, 101:danger, 102:emergency]",
				Schema:      &swagger.Schema{Example: map[string]string{
					"code":  "100",
					"error": "some warning error",
				}},
			}
			op.AppendResponses(responses)
			h.API2.AddOperation(action.Method, path, op)
			h.operations[action] = op
		}
	}
}

func tracePanic(err interface{}, start int, w http.ResponseWriter, r *http.Request) {
	var terr = fmt.Errorf("%v", err)
	if start > 1 {
		terr = trace.DeepError(terr, start, 10)
	}
	if Env.Server.Debug {
		fmt.Fprint(w, terr.Error())
		log.Emergency.Println(terr.Error())
	} else {
		log.Danger.Printf("url: %s%s ip: %s\n panic:%s\n", r.Method, r.RequestURI, GetIP(r), terr.Error())
		http.Error(w, http.StatusText(500), http.StatusInternalServerError)
	}
}

func handleError(cerr *codeError, res *response, request *http.Request) {
	if Env.Server.Debug {
		res.WriteHeader(http.StatusInternalServerError)
		SendJson(map[string]string{
			"code":  cerr.code.String(),
			"error": trace.IgnoreTrace(cerr.err).Error(),
		}, res, true)
	} else {
		http.Error(res, http.StatusText(500), http.StatusInternalServerError)
	}
	var logger *log2.Logger
	switch cerr.code {
	case ErrWarning:
		logger = log.Warning
	case ErrDanger:
		logger = log.Danger
	case ErrEmergency:
		logger = log.Emergency
	default:
		logger = log.Warning
	}
	logger.Printf("code: [%d] uri: [%s]: error: %s\n", res.statusCode, request.Host + request.RequestURI, cerr.err)
}

type response struct {
	http.ResponseWriter
	statusCode int
}

func (r *response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	if hijacker, ok := r.ResponseWriter.(http.Hijacker); ok {
		return hijacker.Hijack()
	} else {
		return nil, nil, fmt.Errorf("interface conversion: %v is not http.Hijacker: missing method Hijack", r.ResponseWriter)
	}
}

func (r *response) Header() http.Header {
	return r.ResponseWriter.Header()
}

func (r *response) Write(data []byte) (int, error) {
	return r.ResponseWriter.Write(data)
}

func (r *response) WriteHeader(statusCode int) {
	r.ResponseWriter.WriteHeader(statusCode)
	r.statusCode = statusCode
}

func (h *HttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	res := &response{ResponseWriter: w, statusCode: 200}
	if Env.Server.LogCostTime {
		begin := time.Now()
		defer func() {
			log.Info.Printf("[%d] cost time: %v uri: [%s] %s\n", res.statusCode, time.Since(begin), r.Method, r.Host + r.RequestURI)
		}()
	}
	if tpl := h.TplContainer.Match(r.URL.Path); tpl != nil {
		err := tpl.Serve(w, r, Env.Server.Debug)
		if err != nil {
			handleError(&codeError{code: ErrDanger, err: err}, res, r)
		}
	} else {
		if act, params := h.router.Match(r.Method + r.URL.Path); act != nil {
			act := act.(*Action)
			ctx := initContext(w, r, act.parser, params, h.SessionClient)
			defer func() {
				// save persistent session
				if ctx.session != nil {
					err := h.SessionClient.SaveSession(ctx.session)
					if err != nil {
						err = fmt.Errorf("save session got error: %s", err)
						handleError(&codeError{code: ErrDanger, err: err}, res, r)
					}
				}
				for _, deferFunc := range ctx.defers {
					deferFunc()
				}
				err := recover()
				if err != nil {
					if codeErr, ok := err.(*codeError); ok {
						handleError(codeErr, res, r)
					} else {
						tracePanic(err, 5, w, r)
					}
				}
			}()
			act.serve(h.TplHandler, ctx)
		} else {
			h.NotFoundHandler(w, r)
		}
	}
}

func SetAfter(middleware AfterHandler, condition MiddlewareCondition) {
	Handler.SetAfter(middleware, condition)
}

func (h *HttpHandler) SetAfter(middleware AfterHandler, condition MiddlewareCondition) {
	h.SetMiddleware(middleware, condition)
}

func SetBefore(middleware BeforeHandler, condition MiddlewareCondition) {
	Handler.SetBefore(middleware, condition)
}

func (h *HttpHandler) SetBefore(middleware BeforeHandler, condition MiddlewareCondition) {
	h.SetMiddleware(middleware, condition)
}

func SetSecurity(middleware *Security, condition MiddlewareCondition) {
	Handler.SetSecurity(middleware, condition)
}

func (h *HttpHandler) SetSecurity(middleware *Security, condition MiddlewareCondition) {
	h.SetMiddleware(middleware, condition)
}

func SetMiddleware(m Middleware, mc MiddlewareCondition) {
	Handler.SetMiddleware(m, mc)
}

func (h *HttpHandler) SetMiddleware(m Middleware, mc MiddlewareCondition) {
	for _, act := range h.actions {
		if mc(act) {
			// 将安全中间件加入 OpenApi 文档
				if secure, ok := m.(*Security); ok && act.ApiDoc != nil {
					// value 不能为 nil
					for key, value := range secure.Requirement {
						if value == nil {
							secure.Requirement[key] = []string{}
						}
					}
					op := h.operations[act]
					op.Security = append(op.Security, swagger.SecurityRequirement(map[string][]string(secure.Requirement)))
				}
			// 加入 action
			act.addMiddleware(m)
		}
	}
}
